/**
 * Copyright (c) 2016-2021 by the respective copyright holders.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package com.zsmartsystems.zigbee.dongle.telegesis.autocode;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.zsmartsystems.zigbee.dongle.telegesis.autocode.xml.Command;
import com.zsmartsystems.zigbee.dongle.telegesis.autocode.xml.Enumeration;
import com.zsmartsystems.zigbee.dongle.telegesis.autocode.xml.Parameter;
import com.zsmartsystems.zigbee.dongle.telegesis.autocode.xml.ParameterGroup;
import com.zsmartsystems.zigbee.dongle.telegesis.autocode.xml.Protocol;
import com.zsmartsystems.zigbee.dongle.telegesis.autocode.xml.Value;

/**
 *
 * @author Chris Jackson
 *
 */
public class CommandGenerator extends ClassGenerator {
    final String zigbeePackage = "com.zsmartsystems.zigbee";
    final String internalPackage = "com.zsmartsystems.zigbee.dongle.telegesis.internal";
    final String commandPackage = "com.zsmartsystems.zigbee.dongle.telegesis.internal.protocol";
    final String enumPackage = "com.zsmartsystems.zigbee.dongle.telegesis.internal.protocol";

    Map<String, String> events = new TreeMap<String, String>();

    public void go(Protocol protocol) throws FileNotFoundException, UnsupportedEncodingException {
        String packageName;
        String className;
        for (Command command : protocol.commands) {
            packageName = commandPackage;
            if (command.command_parameters.size() > 0) {
                className = "Telegesis" + stringToUpperCamelCase(command.name) + "Command";
            } else {
                className = "Telegesis" + stringToUpperCamelCase(command.name) + "Event";
            }

            createCommandClass(packageName, className, command, command.command_parameters,
                    command.response_parameters);

        }

        for (Enumeration enumeration : protocol.enumerations) {
            createEnumClass(enumeration);
        }

        createFrameClass();
    }

    private void createCommandClass(String packageName, String className, Command command,
            List<ParameterGroup> commandParameterGroup, List<ParameterGroup> responseParameterGroup)
            throws FileNotFoundException, UnsupportedEncodingException {

        // Create a list of async events that we need to handle
        if (className.endsWith("Event")) {
            events.put(command.response_parameters.get(0).prompt, className);
        }

        System.out.println("Processing command class " + command.name + "  [" + className + "()]");

        OutputStream stringWriter = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(stringWriter,false,"UTF-8");

        clearImports();
        // addImport("org.slf4j.Logger");
        // addImport("org.slf4j.LoggerFactory");

        // addImport("java.util.Map");
        // addImport("java.util.HashMap");

        out.println("/**");
        out.println(" * Class to implement the Telegesis command <b>" + command.name + "</b>.");
        out.println(" * <p>");
        if (command.description != null && command.description.length() != 0) {
            outputWithLinebreak(out, "", command.description);
            out.println(" * <p>");
        }
        out.println(" * This class provides methods for processing Telegesis AT API commands.");
        out.println(" * <p>");
        out.println(" * Note that this code is autogenerated. Manual changes may be overwritten.");
        out.println(" *");
        out.println(" * @author Chris Jackson - Initial contribution of Java code generator");
        out.println(" */");

        out.print("public class " + className + " extends TelegesisFrame implements ");

        if (commandParameterGroup.size() > 0) {
            // addImport("com.zsmartsystems.zigbee.dongle.telegesis.internal.protocol.TelegesisCommand");
            out.print("TelegesisCommand ");
        }

        // if (commandParameterGroup.size() > 0 && responseParameterGroup.size() > 0) {
        // out.print(", ");
        // }

        else if (responseParameterGroup.size() > 0) {
            // addImport("com.zsmartsystems.zigbee.dongle.telegesis.internal.protocol.TelegesisResponse");
            out.print("TelegesisEvent ");
        }
        out.println("{");

        for (ParameterGroup group : commandParameterGroup) {
            for (Parameter parameter : group.parameters) {
                if (parameter.auto_size != null) {
                    continue;
                }

                out.println("    /**");
                out.println("     * Command field");
                if (parameter.description != null && parameter.description.length() > 0) {
                    outputWithLinebreak(out, "    ", parameter.description);
                }
                out.println("     */");

                createParameterDefinition(out, "    ", parameter);

                out.println();
            }
        }

        for (ParameterGroup group : responseParameterGroup) {
            // Add flags for 'required' prompts
            if (group.required) {
                out.println("    /**");
                if (group.prompt != null && group.prompt.length() > 0) {
                    out.println("     * " + group.prompt + " required response flag");
                } else {
                    out.println("     * Response field");
                }
                out.println("     */");
                out.println("     private boolean received" + stringToUpperCamelCase(group.prompt) + " = false;");
                out.println();
            }

            // Multiple elements at group level get their own class
            if (Boolean.TRUE.equals(group.multiple)) {
                out.println("    /**");
                if (group.prompt != null && group.prompt.length() > 0) {
                    out.println("     * " + group.prompt + " response field");
                } else {
                    out.println("     * Response field");
                }
                out.println("     */");
                addImport("java.util.List");
                addImport("java.util.ArrayList");
                out.println("    private List<" + stringToUpperCamelCase(group.name) + "> "
                        + stringToLowerCamelCase(group.name) + "s = new ArrayList<" + stringToUpperCamelCase(group.name)
                        + ">();");
                out.println();

                continue;
            }
            for (Parameter parameter : group.parameters) {
                if (parameter.auto_size != null) {
                    continue;
                }

                out.println("    /**");
                if (group.prompt != null && group.prompt.length() > 0) {
                    out.println("     * " + group.prompt + " response field");
                } else {
                    out.println("     * Response field");
                }
                if (Boolean.TRUE.equals(group.multiple)) {
                    out.println("     * Field accepts multiple responses.");
                }
                if (parameter.description != null && parameter.description.length() > 0) {
                    outputWithLinebreak(out, "    ", parameter.description);
                }
                out.println("     */");

                createParameterDefinition(out, "    ", parameter);

                out.println();
            }
        }

        for (ParameterGroup group : commandParameterGroup) {
            createParameterSetter(out, "    ", group.parameters);
        }

        for (ParameterGroup group : responseParameterGroup) {
            // Multiple elements at group level get their own class
            if (Boolean.TRUE.equals(group.multiple)) {
                out.println("    /**");
                out.println("     *");
                out.println("     * @return the " + stringToLowerCamelCase(group.name)
                        + "s as a {@link List} of {@link " + stringToUpperCamelCase(group.name) + "}");
                out.println("     */");
                out.println("    public List<" + stringToUpperCamelCase(group.name) + "> get"
                        + stringToUpperCamelCase(group.name) + "s() {");
                out.println("        return " + stringToLowerCamelCase(group.name) + "s;");
                out.println("    }");
                out.println();

                continue;
            }

            createParameterGetter(out, "    ", group.parameters);
        }

        if (className.endsWith("Command")) {
            out.println("    @Override");
            out.println("    public int[] serialize() {");

            if (commandParameterGroup != null && commandParameterGroup.size() != 0) {
                out.println("        // Serialize the command fields");
                boolean first = true;
                for (ParameterGroup group : commandParameterGroup) {
                    out.println("        serializeCommand(\"" + group.prompt + "\");");

                    for (Parameter parameter : group.parameters) {
                        String valueName = stringToLowerCamelCase(parameter.name);
                        String indent = "        ";
                        if (parameter.optional != null && parameter.optional) {
                            out.println(indent + "if (" + stringToLowerCamelCase(parameter.name) + " != null) {");
                            indent = "            ";

                        }
                        if (first == false || (!group.prompt.endsWith("="))) {
                            if (parameter.terminator != null) {
                                if (parameter.terminator.length() != 0) {
                                    out.println(indent + "serializeDelimiter('" + parameter.terminator + "');");
                                }
                            } else {
                                out.println(indent + "serializeDelimiter();");
                            }
                        }
                        first = false;

                        if (parameter.multiple) {
                            out.println(indent + "boolean first" + stringToUpperCamelCase(parameter.name) + " = true;");
                            out.println(indent + "for (" + getTypeClass(parameter.data_type) + " value : "
                                    + stringToLowerCamelCase(parameter.name) + ") {");
                            out.println(indent + "    if (!first" + stringToUpperCamelCase(parameter.name) + ") {");
                            out.println(indent + "        serializeDelimiter(',');");
                            out.println(indent + "    }");
                            out.println(indent + "    first" + stringToUpperCamelCase(parameter.name) + " = false;");
                            valueName = "value";
                            indent = "            ";
                        } else if (parameter.auto_size != null) {
                            out.println(indent + "serialize" + getTypeSerializer(parameter.data_type) + "("
                                    + stringToLowerCamelCase(parameter.auto_size) + ".length);");
                            continue;
                        }
                        out.println(
                                indent + "serialize" + getTypeSerializer(parameter.data_type) + "(" + valueName + ");");

                        if (parameter.multiple || (parameter.optional != null && parameter.optional)) {
                            out.println("        }");
                        }
                    }
                }
                out.println();
            }

            out.println("        return getPayload();");
            out.println("    }");
        } else {

        }

        out.println();
        out.println("    @Override");
        if (className.endsWith("Event")) {
            out.println("    public void deserialize(int[] data) {");
        }
        if (className.endsWith("Command")) {
            out.println("    public boolean deserialize(int[] data) {");
        }

        if (className.endsWith("Command")) {
            out.println("        // Handle standard status responses (ie. OK / ERROR)");
            out.println("        if (handleIncomingStatus(data)) {");
            if (className.endsWith("Command")) {
                out.println("            return true;");
            } else {
                out.println("            return;");
            }
            out.println("        }");
            out.println();
        }

        out.println("        initialiseDeserializer(data);");
        out.println();

        for (ParameterGroup group : responseParameterGroup) {
            if (group.parameters.size() == 0 && group.required == false && group.complete == false) {
                continue;
            }

            String indent;
            if (group.prompt == null || group.prompt.length() == 0) {
                out.println("        // Deserialize the fields for the response");
                indent = "        ";
            } else {
                out.println("        // Deserialize the fields for the \"" + group.prompt + "\" response");
                if (group.parameters.size() > 0 && group.prompt.endsWith("*") == true) {
                    out.println("        if (testPrompt(data, \"" + group.prompt.substring(0, group.prompt.length() - 1)
                            + "\")) {");
                } else if (group.parameters.size() > 0 && group.prompt.endsWith("=") == false) {
                    out.println("        if (testPrompt(data, \"" + group.prompt + ":\")) {");
                } else {
                    out.println("        if (testPrompt(data, \"" + group.prompt + "\")) {");
                }

                if (group.complete) {
                    out.println("            return true;");
                }
                if (group.required) {
                    out.println("            received" + stringToUpperCamelCase(group.prompt) + " = true;");
                }

                if (!Boolean.TRUE.equals(group.multiple) && group.parameters != null && group.parameters.size() > 0) {
                    if (group.prompt.endsWith("*")) {
                        out.println("            setDeserializer(" + (group.prompt.length() - 1) + ");");
                    } else if (group.prompt.endsWith("=")) {
                        out.println("            setDeserializer(" + (group.prompt.length()) + ");");
                    } else {
                        out.println("            setDeserializer(" + (group.prompt.length() + 1) + ");");
                    }
                }
                indent = "            ";
            }

            if (Boolean.TRUE.equals(group.multiple)) {
                out.println(indent + stringToLowerCamelCase(group.name) + "s.add(new "
                        + stringToUpperCamelCase(group.name) + "(data));");
            } else {
                createDeserializer(out, indent, group);
            }

            if (!(group.prompt == null || group.prompt.length() == 0)) {
                if (className.endsWith("Command") && group.complete == false) {
                    out.println("            return false;");
                }
                out.println("        }");
            }

            if (group.required) {
                out.println("        if (!received" + stringToUpperCamelCase(group.prompt) + ") {");
                out.println("            return false;");
                out.println("        }");
            }

        }

        if (className.endsWith("Command")) {
            out.println();
            out.println("        return false;");
        }

        out.println("    }");

        out.println();
        out.println("    @Override");
        out.println("    public String toString() {");

        int parameterCount = 0;
        for (ParameterGroup group : commandParameterGroup) {
            if (group.parameters != null) {
                parameterCount += group.parameters.size();
            }
        }
        for (ParameterGroup group : responseParameterGroup) {
            if (group.parameters != null) {
                parameterCount += group.parameters.size();
            }
        }

        if (parameterCount == 0 && !className.endsWith("Command")) {
            out.println("        return \"" + className + " []\";");
        } else {
            out.println("        final StringBuilder builder = new StringBuilder("
                    + (className.length() + 3 * ((parameterCount + 1) * 30)) + ");");

            boolean first = true;
            if (className.endsWith("Command") && commandParameterGroup != null && commandParameterGroup.size() > 0
                    && commandParameterGroup.get(0).parameters != null
                    && commandParameterGroup.get(0).parameters.size() != 0) {
                out.println("        // First present the command parameters...");
                out.println("        // Then the responses later if they are available");
                for (ParameterGroup group : commandParameterGroup) {
                    createToString(out, "        ", first, className, group);
                    first = false;
                }
            }

            for (ParameterGroup group : responseParameterGroup) {
                if (group == null || group.parameters == null || group.parameters.isEmpty()) {
                    continue;
                }

                if (Boolean.TRUE.equals(group.multiple)) {
                    if (first) {
                        out.println("        builder.append(\"" + className + " [" + stringToLowerCamelCase(group.name)
                                + "s=\");");
                    } else {
                        out.println("        builder.append(\", " + stringToLowerCamelCase(group.name) + "s=\");");
                    }
                    out.println("        builder.append(" + stringToLowerCamelCase(group.name) + "s);");
                    first = false;
                    continue;
                }
                createToString(out, "        ", first, className, group);
                first = false;
            }

            if (className.endsWith("Command")) {
                if (first) {
                    out.println("        builder.append(\"" + className + " [\");");
                }

                out.println("        if (status != null) {");
                if (first) {
                    out.println("            builder.append(\"status=\");");
                } else {
                    out.println("            builder.append(\", status=\");");
                }
                out.println("            builder.append(status);");
                out.println("        }");
            }

            out.println("        builder.append(']');");
            out.println("        return builder.toString();");
        }
        out.println("    }");

        for (ParameterGroup group : responseParameterGroup) {
            // Multiple response elements at group level get their own class
            if (!Boolean.TRUE.equals(group.multiple)) {
                continue;
            }

            out.println();
            out.println("    /**");
            out.println("     *");
            out.println("     */");
            out.println("    public class " + stringToUpperCamelCase(group.name) + " extends TelegesisFrame {");
            for (Parameter parameter : group.parameters) {
                if (parameter.auto_size != null) {
                    continue;
                }

                out.println("        /**");
                if (parameter.description != null && parameter.description.length() > 0) {
                    outputWithLinebreak(out, "        ", parameter.description);
                }
                out.println("         */");

                createParameterDefinition(out, "        ", parameter);
                out.println();
            }

            out.println("        /**");
            out.println("         * Constructor to deserialize the received data");
            out.println("         */");
            out.println("        " + stringToUpperCamelCase(group.name) + "(final int[] data) {");
            out.println("            initialiseDeserializer(data);");
            if (group.prompt != null && group.parameters != null && group.parameters.size() > 0) {
                out.println("            setDeserializer(" + (group.prompt.length() + 1) + ");");
            }
            createDeserializer(out, "            ", group);
            out.println("        }");

            out.println();

            createParameterGetter(out, "        ", group.parameters);
            // createParameterSetter(out, group.parameters);

            out.println("        @Override");
            out.println("        public String toString() {");

            if (group.parameters == null || group.parameters.size() == 0) {
                out.println("            return \"" + className + " []\";");
            } else {
                out.println("            final StringBuilder builder = new StringBuilder("
                        + (group.name.length() + 3 + ((group.parameters.size() + 1) * 30)) + ");");

                createToString(out, "            ", true, stringToUpperCamelCase(group.name), group);

                out.println("            builder.append(']');");
                out.println("            return builder.toString();");
            }
            out.println("        }");

            out.println("    }");
        }

        out.println("}");

        out.flush();

        File packageFile = new File(sourceRootPath + packageName.replace(".", "/"));
        PrintStream outFile = getClassOut(packageFile, className);

        outputCopywrite(outFile);
        outFile.println("package " + packageName + ";");

        outFile.println();

        outputImports(outFile);

        outFile.println();
        outFile.print(stringWriter.toString());

        outFile.flush();
        outFile.close();

        out.close();
    }

    private void createParameterDefinition(PrintStream out, String indent, Parameter parameter) {
        if (parameter.multiple) {
            addImport("java.util.List");
            addImport("java.util.ArrayList");
            out.println(indent + "private List<" + getTypeClass(parameter.data_type) + "> "
                    + stringToLowerCamelCase(parameter.name) + " = new ArrayList<" + getTypeClass(parameter.data_type)
                    + ">();");
        } else {
            out.print(indent + "private " + getTypeClass(parameter.data_type) + " "
                    + stringToLowerCamelCase(parameter.name));
            if (parameter.defaultValue != null && parameter.defaultValue.length() != 0) {
                out.print(" = " + parameter.defaultValue);
            }
            out.println(";");
        }
    }

    private void createParameterGetter(PrintStream out, String indent, List<Parameter> parameters) {
        for (Parameter parameter : parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            out.println(indent + "/**");
            if (parameter.description != null && parameter.description.length() != 0) {
                outputWithLinebreak(out, indent, parameter.description);
            }
            out.println(indent + " *");
            if (parameter.multiple) {
                out.println(indent + " * @return the " + stringToLowerCamelCase(parameter.name)
                        + " as {@link List} of {@link " + getTypeClass(parameter.data_type) + "}");
            } else {
                out.println(indent + " * @return the " + stringToLowerCamelCase(parameter.name) + " as {@link "
                        + getTypeClass(parameter.data_type) + "}");
            }
            out.println(indent + " */");
            if (parameter.multiple) {
                out.println(indent + "public List<" + getTypeClass(parameter.data_type) + "> get"
                        + stringToUpperCamelCase(parameter.name) + "() {");

            } else {
                out.println(indent + "public " + getTypeClass(parameter.data_type) + " get"
                        + stringToUpperCamelCase(parameter.name) + "() {");
            }
            out.println(indent + "    return " + stringToLowerCamelCase(parameter.name) + ";");
            out.println(indent + "}");
            out.println();
        }
    }

    private void createParameterSetter(PrintStream out, String indent, List<Parameter> parameters) {
        for (Parameter parameter : parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            out.println(indent + "/**");
            if (parameter.description != null && parameter.description.length() != 0) {
                outputWithLinebreak(out, "    ", parameter.description);
            }
            out.println(indent + " *");
            if (parameter.multiple) {
                addImport("java.util.Collection");
                out.println(indent + " * @param " + parameter.name + " the " + stringToLowerCamelCase(parameter.name)
                        + " to add to the {@link Set} as {@link " + getTypeClass(parameter.data_type) + "}");
                out.println(indent + " */");
                out.println(indent + "public void add" + stringToUpperCamelCase(parameter.name) + "("
                        + getTypeClass(parameter.data_type) + " " + stringToLowerCamelCase(parameter.name) + ") {");
                out.println(indent + "    this." + stringToLowerCamelCase(parameter.name) + ".add("
                        + stringToLowerCamelCase(parameter.name) + ");");
                out.println(indent + "}");
                out.println();
                out.println(indent + "/**");
                if (parameter.description != null && parameter.description.length() != 0) {
                    outputWithLinebreak(out, "    ", parameter.description);
                }
                out.println(indent + " *");
                out.println(indent + " * @param " + stringToLowerCamelCase(parameter.name) + " the "
                        + stringToLowerCamelCase(parameter.name) + " to remove to the {@link Set} as {@link "
                        + getTypeClass(parameter.data_type) + "}");
                out.println(indent + " */");
                out.println(indent + "public void remove" + stringToUpperCamelCase(parameter.name) + "("
                        + getTypeClass(parameter.data_type) + " " + stringToLowerCamelCase(parameter.name) + ") {");
                out.println(indent + "    this." + stringToLowerCamelCase(parameter.name) + ".remove("
                        + stringToLowerCamelCase(parameter.name) + ");");
                out.println(indent + "}");
                out.println();
                out.println(indent + "/**");
                if (parameter.description != null && parameter.description.length() != 0) {
                    outputWithLinebreak(out, "    ", parameter.description);
                }
                out.println(indent + " *");
                out.println(indent + " * @param " + stringToLowerCamelCase(parameter.name) + " the "
                        + stringToLowerCamelCase(parameter.name) + " to set to the {@link Set} as {@link "
                        + getTypeClass(parameter.data_type) + "}");
                out.println(indent + " */");
                out.println(indent + "public void set" + stringToUpperCamelCase(parameter.name) + "(Collection<"
                        + getTypeClass(parameter.data_type) + "> " + stringToLowerCamelCase(parameter.name) + ") {");
                out.println(indent + "    this." + stringToLowerCamelCase(parameter.name) + ".addAll("
                        + stringToLowerCamelCase(parameter.name) + ");");
            } else {
                out.println(indent + " * @param " + stringToLowerCamelCase(parameter.name) + " the "
                        + stringToLowerCamelCase(parameter.name) + " to set as {@link "
                        + getTypeClass(parameter.data_type) + "}");
                out.println(indent + " */");
                out.println(indent + "public void set" + stringToUpperCamelCase(parameter.name) + "("
                        + getTypeClass(parameter.data_type) + " " + stringToLowerCamelCase(parameter.name) + ") {");

                if (parameter.minimum != null && parameter.maximum != null) {
                    out.println(indent + "    if (" + stringToLowerCamelCase(parameter.name) + " < " + parameter.minimum
                            + " || " + stringToLowerCamelCase(parameter.name) + " > " + parameter.maximum + ") {");
                    out.println(indent
                            + "        throw(new IllegalArgumentException(\"Illegal value passed for channel. Range is "
                            + parameter.minimum + " to " + parameter.maximum + ".\"));");
                    out.println(indent + "    }");
                } else if (parameter.minimum != null) {
                    out.println(indent + "    if (" + stringToLowerCamelCase(parameter.name) + " < " + parameter.minimum
                            + " || " + stringToLowerCamelCase(parameter.name) + " > " + parameter.maximum + ") {");
                    out.println(indent
                            + "        throw(new IllegalArgumentException(\"Illegal value passed for channel. Value must be greater than "
                            + parameter.minimum + ".\"));");
                    out.println(indent + "    }");
                } else if (parameter.maximum != null) {
                    out.println(indent + "    if (" + stringToLowerCamelCase(parameter.name) + " < " + parameter.minimum
                            + " || " + stringToLowerCamelCase(parameter.name) + " > " + parameter.maximum + ") {");
                    out.println(indent
                            + "        throw(new IllegalArgumentException(\"Illegal value passed for channel. Value must be less than "
                            + parameter.maximum + ".\"));");
                    out.println(indent + "    }");
                }

                out.println(indent + "    this." + stringToLowerCamelCase(parameter.name) + " = "
                        + stringToLowerCamelCase(parameter.name) + ";");
            }
            out.println(indent + "}");
            out.println();
        }
    }

    private void createDeserializer(PrintStream out, String indent, ParameterGroup group) {
        Map<String, String> autoSizers = new HashMap<String, String>();
        String conditional = null;

        int cnt = 0;
        for (Parameter parameter : group.parameters) {
            if (parameter.conditional != null && parameter.conditional.length() != 0) {
                if (conditional == null) {
                    out.print(indent + "if (" + parameter.conditional + ") {");
                    conditional = parameter.conditional;
                    indent = indent + "    ";
                } else {
                    if (!parameter.conditional.equals(conditional)) {
                        // New condition
                        indent = indent.substring(0, indent.length() - 4);
                        out.println(indent + "}");
                        out.println();
                        out.print(indent + "if (" + parameter.conditional + ") {");
                        conditional = parameter.conditional;
                        indent = indent + "    ";
                    }
                }
            } else if (conditional != null) {
                conditional = null;
                indent = indent.substring(0, indent.length() - 4);
                out.println(indent + "}");
            }

            cnt++;
            out.println();
            out.print(indent + "// Deserialize field \"" + parameter.name + "\"");
            if (parameter.optional != null && parameter.optional == true) {
                out.println(" [optional]");
            } else {
                out.println();
            }

            if (cnt < group.parameters.size()) {
                if (parameter.optional != null && parameter.optional == true) {
                    out.println(indent + "pushDeserializer();");
                }
            }

            if (parameter.auto_size != null) {
                out.println(indent + "int " + stringToLowerCamelCase(parameter.name) + " = deserialize"
                        + getTypeSerializer(parameter.data_type) + "();");
                autoSizers.put(parameter.auto_size, parameter.name);
                continue;
            }
            if (autoSizers.get(parameter.name) != null) {
                out.println(indent + parameter.name + "= deserialize" + getTypeSerializer(parameter.data_type) + "("
                        + autoSizers.get(parameter.name) + ");");
                continue;
            }
            if (parameter.data_type.contains("[") && parameter.data_type.contains("]")
                    && !parameter.data_type.contains("[]")) {
                int length = Integer.parseInt(parameter.data_type.substring(parameter.data_type.indexOf("[") + 1,
                        parameter.data_type.indexOf("]")));
                out.println(indent + stringToLowerCamelCase(parameter.name) + " = deserialize"
                        + getTypeSerializer(parameter.data_type) + "(" + length + ");");
                continue;
            }

            if (parameter.multiple) {
                out.println(indent + "while (true) {");
                out.println(indent + "    " + getTypeClass(parameter.data_type) + " tmp"
                        + stringToUpperCamelCase(parameter.name) + " = deserialize"
                        + getTypeSerializer(parameter.data_type) + "();");

                out.println(indent + "    if (tmp" + stringToUpperCamelCase(parameter.name) + " == null) {");
                out.println(indent + "        break;");
                out.println(indent + "    }");
                out.println(indent + "    stepDeserializer();");
                out.println(indent + "    " + stringToLowerCamelCase(parameter.name) + ".add(tmp"
                        + stringToUpperCamelCase(parameter.name) + ");");
                out.println(indent + "}");
            } else {
                out.println(indent + stringToLowerCamelCase(parameter.name) + " = deserialize"
                        + getTypeSerializer(parameter.data_type) + "();");
            }

            // if (parameter.optional != null && parameter.optional == true) {
            // out.println(indent + "if (" + stringToLowerCamelCase(parameter.name) + " == null) {");
            // out.println(indent + " popDeserializer();");
            // out.println(indent + "}");
            // }

            if (cnt < group.parameters.size()) {
                if (parameter.optional != null && parameter.optional == true) {
                    out.println(indent + "if (" + stringToLowerCamelCase(parameter.name) + " == null) {");
                    out.println(indent + "    popDeserializer();");
                    out.println(indent + "} else {");
                    out.println(indent + "    stepDeserializer();");
                    out.println(indent + "}");
                } else {
                    out.println(indent + "stepDeserializer();");
                }

            }
        }

        if (conditional != null) {
            indent = indent.substring(0, indent.length() - 4);
            out.println(indent + "}");
        }
    }

    private void createToString(PrintStream out, String indent, boolean first, String className, ParameterGroup group) {
        for (Parameter parameter : group.parameters) {
            if (parameter.auto_size != null) {
                continue;
            }

            if (first) {
                out.println(indent + "builder.append(\"" + className + " [" + stringToLowerCamelCase(parameter.name)
                        + "=\");");
            } else {
                out.println(indent + "builder.append(\", " + stringToLowerCamelCase(parameter.name) + "=\");");
            }
            first = false;
            if (parameter.data_type.equals("Data")) {
                out.println(indent + "if (" + stringToLowerCamelCase(parameter.name) + " == null) {");
                out.println(indent + "    builder.append(\"null\");");
                out.println(indent + "} else {");
                out.println(indent + "    for (int cnt = 0; cnt < " + stringToLowerCamelCase(parameter.name)
                        + ".length; cnt++) {");
                out.println(indent + "        if (cnt > 0) {");
                out.println(indent + "            builder.append(' ');");
                out.println(indent + "        }");
                out.println(indent + "        builder.append(String.format(\"%02X\", "
                        + formatParameterString(parameter) + "[cnt]));");
                out.println(indent + "    }");
                out.println(indent + "}");
            } else {
                out.println(indent + "builder.append(" + formatParameterString(parameter) + ");");
            }
        }
    }

    private void createEnumClass(Enumeration enumeration) throws FileNotFoundException, UnsupportedEncodingException {
        String className = upperCaseFirstCharacter(enumeration.name);
        System.out.println("Processing enum class " + enumeration.name + "  [" + className + "()]");

        OutputStream stringWriter = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(stringWriter,false,"UTF-8");

        clearImports();

        boolean hasValues = false;
        for (Value value : enumeration.values) {
            if (value.enum_value != null) {
                hasValues = true;
                break;
            }
        }

        if (hasValues) {
            addImport("java.util.Map");
            addImport("java.util.HashMap");
        }

        out.println("/**");
        out.println(" * Class to implement the Telegesis Enumeration <b>" + enumeration.name + "</b>.");
        if (enumeration.description != null && enumeration.description.trim().length() > 0) {
            out.println(" * <p>");
            outputWithLinebreak(out, "", enumeration.description);
        }
        out.println(" * <p>");
        out.println(" * Note that this code is autogenerated. Manual changes may be overwritten.");
        out.println(" *");
        out.println(" * @author Chris Jackson - Initial contribution of Java code generator");
        out.println(" */");

        out.println("public enum " + className + " {");

        out.println("    /**");
        out.println("     * Default unknown value");
        out.println("     */");
        if (hasValues) {
            out.println("    UNKNOWN(-1),");
        } else {
            out.println("    UNKNOWN,");
        }

        boolean first = true;
        for (Value value : enumeration.values) {
            if (!first) {
                out.println(",");
            }
            first = false;
            out.println();
            out.println("    /**");
            if (hasValues) {
                outputWithLinebreak(out, "    ", "[" + value.enum_value + "] " + value.description);
            } else {
                outputWithLinebreak(out, "    ", value.description);
            }
            out.println("     */");
            if (hasValues) {
                out.print(
                        "    " + stringToConstant(value.name) + "(0x" + String.format("%04X", value.enum_value) + ")");
            } else {
                out.print("    " + stringToConstant(value.name));
            }
        }

        out.println(";");

        if (hasValues) {
            out.println();
            out.println("    /**");
            out.println("     * A mapping between the integer code and its corresponding type to");
            out.println("     * facilitate lookup by code.");
            out.println("     */");
            out.println("    private static Map<Integer, " + className + "> codeMapping;");
            out.println();

            out.println("    private int key;");
            out.println();

            out.println("    private " + className + "(int key) {");
            out.println("        this.key = key;");
            out.println("    }");
            out.println();

            out.println("    private static void initMapping() {");
            out.println("        codeMapping = new HashMap<Integer, " + className + ">();");
            out.println("        for (" + className + " s : values()) {");
            out.println("            codeMapping.put(s.key, s);");
            out.println("        }");
            out.println("    }");
            out.println();

            out.println("    /**");
            out.println("     * Lookup function based on the type code. Returns null if the code does not exist.");
            out.println("     *");
            out.println("     * @param " + lowerCaseFirstCharacter(className));
            out.println("     *            the code to lookup");
            out.println("     * @return enumeration value.");
            out.println("     */");
            out.println("    public static " + className + " get" + className + "(int "
                    + lowerCaseFirstCharacter(className) + ") {");
            out.println("        if (codeMapping == null) {");
            out.println("            initMapping();");
            out.println("        }");
            out.println();

            out.println("        if (codeMapping.get(" + lowerCaseFirstCharacter(className) + ") == null) {");
            out.println("            return UNKNOWN;");
            out.println("        }");
            out.println();

            out.println("        return codeMapping.get(" + lowerCaseFirstCharacter(className) + ");");
            out.println("    }");
            out.println();
            out.println("    /**");
            out.println("     * Returns the Telegesis protocol defined value for this enum");
            out.println("     *");
            out.println("     * @return the Telegesis enumeration key");
            out.println("     */");
            out.println("    public int getKey() {");
            out.println("        return key;");
            out.println("    }");
        }
        out.println("}");

        out.flush();

        File packageFile = new File(sourceRootPath + enumPackage.replace(".", "/"));
        PrintStream outFile = getClassOut(packageFile, className);

        outputCopywrite(outFile);
        outFile.println("package " + enumPackage + ";");

        outFile.println();

        outputImports(outFile);

        outFile.println();
        outFile.print(stringWriter.toString());

        outFile.flush();
        outFile.close();

        out.close();
    }

    private int hashString(String in) {
        int hash = 0;
        int multiplier = 1;
        for (byte val : in.getBytes()) {
            hash += (val & 0xff) * multiplier;
            int shifted = multiplier << 5;
            multiplier = shifted - multiplier;
        }

        return hash;
    }

    private void createFrameClass() throws FileNotFoundException, UnsupportedEncodingException {
        // Do a quick check to make sure we don't have duplicate hash's
        List<Integer> hashcodes = new ArrayList<Integer>();
        for (String event : events.values()) {
            int hash = hashString(event);
            if (hashcodes.contains(hash)) {
                System.out.println("*********** OOOOOPS - duplicate hashcode!!!  ");
            }
            hashcodes.add(hash);
        }

        OutputStream stringWriter = new ByteArrayOutputStream();
        PrintStream out = new PrintStream(stringWriter,false,"UTF-8");

        clearImports();
        addImport(commandPackage + ".TelegesisEvent");
        addImport("java.lang.reflect.Constructor");
        addImport("org.slf4j.Logger");
        addImport("org.slf4j.LoggerFactory");
        addImport("java.util.Map");
        addImport("java.util.concurrent.ConcurrentHashMap");

        for (String event : events.values()) {
            addImport(commandPackage + "." + event);
        }
        out.println();

        out.println("/**");
        out.println(" * Helper factory class to create Telegesis event classes.");
        out.println(" * <p>");
        out.println(" * Note that this code is autogenerated. Manual changes may be overwritten.");
        out.println(" *");
        out.println(" * @author Chris Jackson - Initial contribution of Java code generator");
        out.println(" */");

        out.println("public class TelegesisEventFactory {");
        out.println("    private final static Logger logger = LoggerFactory.getLogger(TelegesisEventFactory.class);");
        out.println();

        out.println("    private static Map<Integer, Class<?>> events = new ConcurrentHashMap<Integer, Class<?>>();");
        out.println();

        Map<Integer, String> sortedEvents = new TreeMap<Integer, String>();
        for (String event : events.keySet()) {
            if (sortedEvents.containsKey(hashString(event))) {
                System.out
                        .println("Duplicate hash " + events.get(event) + " === " + sortedEvents.get(hashString(event)));
            }
            sortedEvents.put(hashString(event), events.get(event));
        }

        out.println("    static {");
        for (Integer event : sortedEvents.keySet()) {
            out.println("        events.put(" + String.format("0x%08X", event) + ", " + sortedEvents.get(event)
                    + ".class);");
        }
        out.println("    }");
        out.println();

        out.println("    public static TelegesisEvent getTelegesisFrame(int[] data) {");

        out.println("        // Create the hash of the prompt");
        out.println("        int hash = 0;");
        out.println("        int multiplier = 1;");
        out.println("        for (int value : data) {");
        out.println("            if (value == '\\n' || value == ':' || value == '=') {");
        out.println("                break;");
        out.println("            }");
        out.println();

        out.println("            hash += (value & 0xff) * multiplier;");
        out.println("            int shifted = multiplier << 5;");
        out.println("            multiplier = shifted - multiplier;");

        out.println("        }");
        out.println();

        out.println("        Class<?> telegesisClass = events.get(hash);");
        out.println("        if (telegesisClass == null) {");
        out.println("            return null;");
        out.println("        }");
        out.println();
        out.println("        Constructor<?> ctor;");
        out.println("        try {");
        out.println("            ctor = telegesisClass.getConstructor();");
        out.println("            TelegesisEvent telegesisEvent = (TelegesisEvent) ctor.newInstance();");
        out.println("            telegesisEvent.deserialize(data);");
        out.println("            return telegesisEvent;");
        out.println("        } catch (Exception e) {");
        out.println("            logger.debug(\"Error creating instance of Telegesis event\", e);");
        out.println("        }");
        out.println();
        out.println("        return null;");
        out.println("    }");

        out.println("}");

        out.flush();

        File packageFile = new File(sourceRootPath + internalPackage.replace(".", "/"));
        PrintStream outFile = getClassOut(packageFile, "TelegesisEventFactory");

        outputCopywrite(outFile);
        outFile.println("package " + internalPackage + ";");

        outFile.println();

        outputImports(outFile);

        outFile.println();
        outFile.print(stringWriter.toString());

        outFile.flush();
        outFile.close();

        out.close();
    }

    protected String getTypeClass(String dataType) {
        String dataTypeLocal = new String(dataType);

        switch (dataTypeLocal) {
            case "Data":
                return "int[]";
            case "Dec8":
            case "Int4":
            case "Int8":
            case "SInt8":
            case "Int16":
            case "Integer":
                return "Integer";
            case "Int32":
                return "Long";
            case "Boolean":
                return "Boolean";
            case "String":
                return "String";
            case "ZigBeeKey":
                addImport(zigbeePackage + ".security.ZigBeeKey");
                return "ZigBeeKey";
            case "IeeeAddress":
                addImport(zigbeePackage + ".IeeeAddress");
                return "IeeeAddress";
            case "ExtendedPanId":
                addImport(zigbeePackage + ".ExtendedPanId");
                return "ExtendedPanId";
            case "ZigBeeDeviceAddress":
                addImport(zigbeePackage + ".ZigBeeDeviceAddress");
                return "ZigBeeDeviceAddress";
            case "ZigBeeGroupAddress":
                addImport(zigbeePackage + ".ZigBeeGroupAddress");
                return "ZigBeeGroupAddress";
            default:
                addImport(enumPackage + "." + dataTypeLocal);
                return dataTypeLocal;
        }
    }

    protected String getTypeSerializer(String dataType) {
        String dataTypeLocal = new String(dataType);

        switch (dataTypeLocal) {
            case "Data":
                return "Data";
            default:
                return dataTypeLocal;
        }
    }
}
